﻿using ASmile.Extend;
using ASmile.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace ASmile.Win
{
    public class WebApiAttribute : Attribute
    {
        public WebApiAttribute() { }

        public WebApiAttribute(string url)
        {
            Url = url;
        }
        public WebApiAttribute(string controllerName, string actionName)
        {
            Url = $"api/{controllerName}/{actionName}";
        }
        public string Url { set; get; }
    }

    /// <summary>
    /// WebApi 请求
    /// </summary>
    public class WebApiClient
    {
        public WebApiClient() { }

        public WebApiClient(string rootUrl)
        {
            RootUrl = rootUrl;
        }

        public WebApiClient(string rootUrl, string baseUrl)
        {
            RootUrl = rootUrl;
            BaseUrl = baseUrl;
        }

        const string HttpBoundary = "------------UpdateFileBoundary";

        protected Dictionary<string, string> Headers { get; } = new Dictionary<string, string>();

        protected bool IsHttps { get; private set; }

        protected int RequestTimeOut { set; get; } = 1000 * 30;

        protected int ReadWriteTimeout { set; get; } = 1000 * 30;
        /// <summary>
        /// 当请求失败(异常)的触发
        /// </summary>
        protected virtual void RequestFailHandle(int statusCode, Exception exception)
        { }

        //protected virtual string BaseUrl { set; get; }
        /// <summary>
        /// 服务器主地址
        /// </summary>
        protected virtual string RootUrl { get; set; }
        /// <summary>
        /// action　前面的路径
        /// </summary>
        protected virtual string BaseUrl { get; }

        protected internal void SetHttpHeader(string name, string value)
        {
            Headers[name] = value;
        }

        protected virtual bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
        {
            return true;
        }

        protected virtual void RequestBefore() { }

        protected string MergeUrl(string srcUrl, string tarUrl)
        {
            if (!srcUrl.EndsWith("/")) srcUrl += "/";
            if (tarUrl.StartsWith("/"))
            {
                return srcUrl + tarUrl.Substring(1);
            }
            else
            {
                return srcUrl + tarUrl;
            }
        }

        string GetResponseResult(HttpWebResponse response)
        {
            using (Stream rspStream = response.GetResponseStream())
            {
                using (StreamReader sr = new StreamReader(rspStream, Encoding.UTF8))
                {
                    return sr.ReadToEnd();
                }
            }
        }

        /// <summary>
        /// 发起post请求，body 为 数据流
        /// </summary>
        protected int HttpRequest(string url, string method, byte[] body, out string result, Action<HttpWebRequest> SetRequestBefore = null)
        {
            IsHttps = url.ToLower().StartsWith("https");
            if (IsHttps)
            {
                //这一句一定要写在创建连接的前面。使用回调的方法进行证书验证。
                ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(CheckValidationResult);
                ServicePointManager.SecurityProtocol = (SecurityProtocolType)192 | (SecurityProtocolType)768 | (SecurityProtocolType)3072;
            }
            url = MergeUrl(RootUrl, url);
            //初始化对像，并设置请求的URL地址
            var request = (HttpWebRequest)WebRequest.Create(url);
            //request.ClientCertificates.Add(new X509Certificate(item.CerPath));
            try
            {
                RequestBefore();
                foreach (var item in Headers)
                {
                    request.Headers.Add(item.Key, item.Value);
                }
                Headers.Clear();
                request.Method = method; // "POST";
                request.KeepAlive = true;
                request.Timeout = RequestTimeOut;
                request.ReadWriteTimeout = ReadWriteTimeout;
                SetRequestBefore?.Invoke(request);
                if (request.ContentType.IsEmpty()) request.ContentType = "application/json";
                if (body != null && body.Length > 0)
                {
                    request.ContentLength = body.Length;
                    request.GetRequestStream().Write(body, 0, body.Length);
                }
                else
                {
                    request.ContentLength = 0;
                }
            }
            catch (Exception ex)
            {
                result = ex.Message;
                RequestFailHandle(0, ex);
                LogHelper.LogObj.Error($"GetRequestStream 发生异常, URL:{url}", ex);
                return 0;
            }
            HttpWebResponse rsp = null;
            try
            {
                rsp = (HttpWebResponse)request.GetResponse();
                result = GetResponseResult(rsp);
                //using (Stream rspStream = rsp.GetResponseStream())
                //{
                //    using (StreamReader sr = new StreamReader(rspStream, Encoding.UTF8))
                //    {
                //        result = sr.ReadToEnd();
                //    }
                //}
                return (int)rsp.StatusCode;
            }
            catch (WebException ex)
            {
                rsp = ex.Response as HttpWebResponse;
                result = ex.Message;                
                var statusCode = (int?)rsp?.StatusCode ?? 0;
                RequestFailHandle(statusCode, ex);
                string errMsg = "";
                if (statusCode == 401)
                {
                    errMsg = GetResponseResult(rsp);
                    
                }
                LogHelper.LogObj.Error($"GetResponseStream 发生异常,(result: {errMsg}) , URL:{url}", ex);
                return statusCode;
            }
            finally
            {
                rsp?.Close();
            }
        }

        protected int PostRequest(string url, byte[] body, out string result, Action<HttpWebRequest> SetRequestBefore = null)
        {
            return HttpRequest(url, "POST", body, out result, SetRequestBefore);
        }

        protected int PostRequest(string url, string body, out string result, Action<HttpWebRequest> SetRequestBefore = null)
        {
            byte[] buffer;
            if (!string.IsNullOrWhiteSpace(body))
            {
                buffer = Encoding.UTF8.GetBytes(body);
            }
            else
            {
                buffer = null;
            }
            return HttpRequest(url, "POST", buffer, out result, SetRequestBefore);
        }

        protected int PostRequest(string url, string body, out string result)
        {
            return PostRequest(url, body, out result);
        }

        protected internal TRet PostRequest<TRet>(string url, object jsonObj, Action<HttpWebRequest> SetRequestBefore = null)
            where TRet : ApiResult, new()
        {
            string jsonBody = JsonHelper.ToJson(jsonObj);

            var statusCode = PostRequest(url, jsonBody, out string result, SetRequestBefore);

            if (statusCode != 200)
            {
                //LogHelper.LogObj.Error($"HTTP请求失败，状态码:{statusCode},url: {url} msg:{result}");
                string msg = result;
                return new TRet
                {
                    code = ApiResult.CODE_HTTP_FAIL,
                    msg = msg
                };
            }

            if (result.IsEmpty())
            {
                LogHelper.LogObj.Error($"HTTP请求失败，返回内容为空,url: {url}，req: {jsonBody}");
                return new TRet
                {
                    code = ApiResult.CODE_HTTP_FAIL,
                    msg = $"请求服务器失败，返回内容为空"
                };
            }

            TRet ret = JsonHelper.JsonToObject<TRet>(result);
            if (ret == null)
            {
                LogHelper.LogObj.Error($"HTTP请求失败，转换格式不正确,\nurl: {url}\nmsg:{result}");

                return new TRet
                {
                    code = ApiResult.CODE_HTTP_FAIL,
                    msg = $"请求服务器失败，转换格式不正确"
                };
            }
            return ret;
        }

        protected TRet PostFile<TRet>(string url, byte[] fileBytes, string fileName)
            where TRet : ApiResult, new()
        {
            BytesEncodeHelper byteObj = new BytesEncodeHelper();

            byteObj.ClearData();
            byteObj
                .AddString($"--{HttpBoundary}").AddLine()
                .AddString($"Content-Disposition:  form-data; name=\"file\";  filename=\"{fileName}\"").AddLine()
                .AddString($"Content-Type: application/octet-stream").AddLine(2);

            byteObj.AddBytes(fileBytes);
            byteObj.AddLine().AddString($"--{HttpBoundary}--").AddLine(2);


            var bodyData = byteObj.GetResult().ToArray();

            var statusCode =
            PostRequest(url, fileBytes, out string r, (request) =>
            {
                request.ContentType = $"multipart/form-data; boundary={HttpBoundary}";
            });
            if (statusCode != 200)
            {
                LogHelper.LogObj.Error($"HTTP上传文件失败，url: {url}");

                return new TRet
                {
                    code = ApiResult.CODE_HTTP_FAIL,
                    msg = r
                };
            }
            TRet ret = JsonHelper.JsonToObject<TRet>(r);
            return ret;
        }

        public static string UrlEncode(string str)
        {
            StringBuilder sb = new StringBuilder();
            byte[] byStr = Encoding.UTF8.GetBytes(str); //默认是System.Text.Encoding.Default.GetBytes(str)
            for (int i = 0; i < byStr.Length; i++)
            {
                sb.Append(@"%" + Convert.ToString(byStr[i], 16));
            }

            return sb.ToString();
        }

        protected TRet PostRequestAction<TRet>(int frameIndex, params object[] args)
             where TRet : ApiResult, new()
        {
            StackTrace trace = new StackTrace();
            var frame = trace.GetFrame(frameIndex);
            var method = frame.GetMethod();

            var reqArg = ReflectionHelper.GetMethodAttribute<WebApiAttribute>(method, false);
            string url = null;
            if (reqArg != null && reqArg.Url.IsNotEmpty())
            {
                url = reqArg.Url;
            }

            if (url.IsEmpty())
            {
                var actionName = method.Name;
                url = MergeUrl(BaseUrl, actionName);
            }

            var parameterInfos = method.GetParameters();
            Dictionary<string, object> urlParamters = new Dictionary<string, object>();
            if (parameterInfos?.Length > 0 && parameterInfos.Length == args.Length) //&& !parameterInfos.Any(s => s.ParameterType.IsClass && s.ParameterType!=typeof(string)))
            {
                for (int i = 0; i < parameterInfos.Length; i++)
                {
                    var pInfo = parameterInfos[i];
                    var pValue = args[i];
                    if (pValue is string || (!pInfo.ParameterType.IsClass && pValue.GetType() == pInfo.ParameterType))
                    {
                        urlParamters[pInfo.Name] = pValue;
                    }
                }
            }
            if (urlParamters.Count > 0)
            {
                string paramters = "";
                foreach (var item in urlParamters)
                {
                    if (paramters.Length > 0)
                    {
                        paramters += "&";
                    }
                    if (item.Value is DateTime dt)
                    {
                        paramters += $"{item.Key}={dt:yyyy-MM-dd HH:mm:ss}";
                    }
                    else
                    {
                        paramters += $"{item.Key}={item.Value}";
                    }
                }

                url += "?" + paramters;
                return PostRequest<TRet>(url, null);
            }
            else
            {
                object body;
                if (args != null && args.Length > 0) body = args[0];
                else body = null;
                return PostRequest<TRet>(url, body);
            }
        }

        protected TModel GetModel<TModel>(params object[] arg)
        {
            var ret = PostRequestAction<ApiResult<TModel>>(2, arg);
            return ret.data;
        }

        protected List<TList> GetList<TList>(out int total, params object[] arg) where TList : class, new()
        {
            var ret = PostRequestAction<ApiResultList<TList>>(2, arg);
            total = ret.count;
            return ret.data;
        }

        //兼容原实现
        protected List<TList> GetList<TList>(object arg, out int total) where TList : class, new()
        {
            var ret = PostRequestAction<ApiResultList<TList>>(2, arg);
            total = ret.count;
            return ret.data;
        }

        protected List<TList> GetList<TList>(params object[] arg) where TList : class, new()
        {
            var ret = PostRequestAction<ApiResultList<TList>>(2, arg);
            return ret.data;
        }

        protected ApiResult GetResult(params object[] arg)
        {
            return PostRequestAction<ApiResult>(2, arg);
        }

        protected ApiResult<T> GetResult<T>(params object[] arg)
        {
            return PostRequestAction<ApiResult<T>>(2, arg);
        }

        protected ApiResultList<T> GetResultList<T>(params object[] arg)
        {
            return PostRequestAction<ApiResultList<T>>(2, arg);
        }
    }
}